react v16做来哪些大更新
- 采用了称为“Fiber”的全新的内部架构。
React Fiber是什么?
简单来说,官方对 Fiber 的一句话解释是“React Fiber是对核心算法的一次重新实现”。通过“Fiber”,React 使得大量的计算可以被拆解分片,异步化。每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做。从而保证了渲染的帧率,提高应用响应性。 - Portals新渲染方法(新增了一个顶级 API: ReactDOM.createPortal)
- render方法返回新的类型:代码块(比如数组,需要添加key)和字符串
- 更好的错误处理方式(增加了 componentDidCatch(error, info) 的生命周期函数)
- 减小了文件大小
- 更换协议为MIT
。。。
关于react v16.3生命周期的变化
为什么被废弃?
上面我们提到了react v16 使用‘Fiber’对 核心算法的一次重新实现,这也是生命周期废弃的一个重要原因之一。因为在现有的React中,每个生命周期函数在一个加载或者更新过程中绝对只会被调用一次;在React Fiber中,不再是这样了,第一阶段中的生命周期函数在一次加载和更新过程中可能会被多次调用!那么多次调用的话主要会对 componentWillMount 和 componentWillUpdate 这两个生命周期内部的实现造成不可预知的影响。
被废弃的生命周期
- componentWillReceiveProps
- componentWillMount
- componentWillUpdate
新增的生命周期函数
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
废弃后升级方案
componentWillMount
首页无数据白屏
在 React 应用中,许多开发者为了避免第一次渲染时页面因为没有获取到异步数据导致的白屏,而将数据请求部分的代码放在了 componentWillMount 中,希望可以避免白屏并提早异步请求的发送时间。但是要知道,在componentWillMount里发起AJAX,不管多快得到结果也赶不上首次render,而且componentWillMount在服务器端渲染也会被调用到,这样的IO操作放在componentDidMount里更合适。
事件订阅
另一个常见的用例是在 componentWillMount 中订阅事件,并在 componentWillUnmount 中取消掉相应的事件订阅。但事实上 React 并不能够保证在 componentWillMount 被调用后,同一组件的 componentWillUnmount 也一定会被调用。一个当前版本的例子如服务端渲染时,componentWillUnmount 是不会在服务端被调用的,所以在 componentWillMount 中订阅事件就会直接导致服务端的内存泄漏。另一方面,在未来 React 开启异步渲染模式后,在 componentWillMount 被调用之后,组件的渲染也很有可能会被其他的事务所打断,导致 componentWillUnmount 不会被调用。而 componentDidMount 就不存在这个问题,在 componentDidMount 被调用后,componentWillUnmount 一定会随后被调用到,并根据具体代码清除掉组件中存在的事件订阅。
升级方案
将现有 componentWillMount 中的代码迁移至 componentDidMount 即可。
componentWillReceiveProps
更新由 props 决定的 state 及处理特定情况下的回调
在老版本的 React 中,如果组件自身的某个 state 跟其 props 密切相关的话,一直都没有一种很优雅的处理方式去更新 state,而是需要在 componentWillReceiveProps 中判断前后两个 props 是否相同,如果不同再将新的 props 更新到相应的 state 上去。这样做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,另一方面也会增加组件的重绘次数。
新版提供了更为简洁的生命周期 getDerivedStateFromProps:
1 | static getDerivedStateFromProps(nextProps, prevState) |
一个简单的例子如下:
1 | // before |
通常来讲,在 componentWillReceiveProps 中,我们一般会做以下两件事,一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转等。在老版本的 React 中,这两件事我们都需要在 componentWillReceiveProps 中去做。而在新版本中,官方将更新 state 与触发回调重新分配到了 getDerivedStateFromProps 与 componentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去做其他一些让组件自身状态变得更加不可预测的事情。
升级方案
将现有 componentWillReceiveProps 中的代码根据更新 state 或回调,分别在 getDerivedStateFromProps 及 componentDidUpdate 中进行相应的重写即可,注意新老生命周期函数中 prevProps,this.props,nextProps,prevState,this.state 的不同。
componentWillUpdate
处理因为 props 改变而带来的副作用
与 componentWillReceiveProps 类似,许多开发者也会在 componentWillUpdate 中根据 props 的变化去触发一些回调。但不论是 componentWillReceiveProps 还是 componentWillUpdate,都有可能在一次更新中被调用多次,也就是说写在这里的回调函数也有可能会被调用多次,这显然是不可取的。与 componentDidMount 类似,componentDidUpdate 也不存在这样的问题,一次更新中 componentDidUpdate 只会被调用一次,所以将原先写在 componentWillUpdate 中的回调迁移至 componentDidUpdate 就可以解决这个问题。
在组件更新前读取 DOM 元素状态
另一个常见的 componentWillUpdate 的用例是在组件更新前,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理。但在 React 开启异步渲染模式后,render 阶段和 commit 阶段之间并不是无缝衔接的,也就是说在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在
componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。
而 getSnapshotBeforeUpdate 这个新生命周期可以解决这个问题:
1 | getSnapshotBeforeUpdate(prevProps, prevState) { |
与 componentWillUpdate 不同,getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。虽然 getSnapshotBeforeUpdate 不是一个静态方法,但我们也应该尽量使用它去返回一个值。这个值会随后被传入到 componentDidUpdate 中,然后我们就可以在 componentDidUpdate 中去更新组件的状态,而不是在 getSnapshotBeforeUpdate 中直接更新组件状态。这个函数的使用场景比较少。
升级方案
将现有的 componentWillUpdate 中的回调函数迁移至 componentDidUpdate。如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。
升级前后生命周期对比
以上的这些生命周期函数的改动,一直要到 React 17.0 中才会实装,这给广大的 React 开发者们预留了充足的时间去适应这次改动。
下面借用两个图来展示升级前后生命周期的对比。
升级前:
升级后: